package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
	"github.com/golang/protobuf/ptypes/empty"
	"go.opentelemetry.io/otel"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"math/rand"
	"mxshop_srvs/order_srv/global"
	"mxshop_srvs/order_srv/model"
	"mxshop_srvs/order_srv/proto"
	"time"
)

/*
做业务逻辑处理 基本是改动这里
*/

type OrderServer struct {
	proto.UnimplementedOrderServer
}

// GenerateOrderSn 订单号的生成
func GenerateOrderSn(userId int32) string {
	// 订单号的生成规则
	/*
		年月日时分秒+用户id+2位随机数
	*/
	now := time.Now()
	rand.Seed(time.Now().UnixNano()) // 设置随机种子
	orderSn := fmt.Sprintf(
		"%d%d%d%d%d%d%d%d",
		now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Nanosecond(),
		userId, rand.Intn(90)+10,
	)
	return orderSn
}

// CartItemList 获取用户的购物车列表
func (*OrderServer) CartItemList(ctx context.Context, req *proto.UserInfo) (*proto.CartItemListResponse, error) {
	var shopCarts []model.ShoppingCart
	var rsp proto.CartItemListResponse

	if result := global.DB.Where(&model.ShoppingCart{User: req.Id}).Find(&shopCarts); result.Error != nil {
		return nil, result.Error
	} else {
		rsp.Total = int32(result.RowsAffected)
	}

	for _, shopCart := range shopCarts {
		rsp.Data = append(rsp.Data, &proto.ShopCartInfoResponse{
			Id:      shopCart.ID,
			UserId:  shopCart.User,
			GoodsId: shopCart.Goods,
			Nums:    shopCart.Nums,
			Checked: shopCart.Checked,
		})
	}
	return &rsp, nil
}

// CreateCartItem 将商品添加到购物车
func (*OrderServer) CreateCartItem(ctx context.Context, req *proto.CartItemRequest) (*proto.ShopCartInfoResponse, error) {
	// 1. 购物车中原本没有这件商品 - 新建一个记录 2. 这个商品之前添加到了购物车 合并
	var shopCart model.ShoppingCart

	if result := global.DB.Where(&model.ShoppingCart{
		User:  req.UserId,
		Goods: req.GoodsId,
	}).First(&shopCart); result.RowsAffected == 1 {
		// 如果记录已经存在, 则合并购物车记录 更新操作
		shopCart.Nums += req.Nums
	} else {
		// 插入操作
		shopCart.User = req.UserId
		shopCart.Goods = req.GoodsId
		shopCart.Nums = req.Nums
		shopCart.Checked = false
	}
	global.DB.Save(&shopCart)
	return &proto.ShopCartInfoResponse{Id: shopCart.ID}, nil
}

// UpdateCartItem 更新购物车记录, 更新数量和选中状态
func (*OrderServer) UpdateCartItem(ctx context.Context, req *proto.CartItemRequest) (*empty.Empty, error) {
	var shopCart model.ShoppingCart

	if result := global.DB.Where("goods=? and user=?", req.GoodsId, req.UserId).First(&shopCart); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "用户购物车记录不存在")
	}
	shopCart.Checked = req.Checked
	if req.Nums > 0 {
		shopCart.Nums = req.Nums
	}
	global.DB.Save(&shopCart)

	return &empty.Empty{}, nil
}

func (*OrderServer) DeleteCartItem(ctx context.Context, req *proto.CartItemRequest) (*empty.Empty, error) {
	if result := global.DB.Where("goods=? and user=?", req.GoodsId, req.UserId).Delete(&model.ShoppingCart{}); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "用户购物车记录不存在")
	}
	return &empty.Empty{}, nil
}

type OrderListener struct {
	Code        codes.Code
	Detail      string
	ID          int32
	OrderAmount float32
	Ctx         context.Context
}

func (o *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)

	// 从购物车中获取选中的商品
	_, spanSelectShoppingCart := otel.Tracer("").Start(o.Ctx, "select_ShoppingCart")
	var goodsIds []int32
	var shopCarts []model.ShoppingCart
	goodsNumsMap := make(map[int32]int32) // 防止被赋值的 null 类型
	if result := global.DB.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Find(&shopCarts); result.RowsAffected == 0 {
		o.Code = codes.InvalidArgument
		o.Detail = "[CreateOrder] 没有选中结算的商品"
		return primitive.RollbackMessageState // 失败 撤销 事务消息
	}
	spanSelectShoppingCart.End()

	for _, shopCart := range shopCarts {
		goodsIds = append(goodsIds, shopCart.Goods) // 获取 购物车中商品id
		goodsNumsMap[shopCart.Goods] = shopCart.Nums
	}

	// 跨服务调用商品服务 - gin
	// 商品的价格需要自己查询 - 访问商品服务 (跨商品服务)
	var orderAmount float32
	var orderGoods []*model.OrderGoods
	var goodsInvInfo []*proto.GoodsInvInfo
	goods, err := global.GoodsSrvClient.BatchGetGoods(o.Ctx, &proto.BatchGoodsIdInfo{Id: goodsIds})
	if err != nil {
		o.Code = codes.Internal
		o.Detail = "[CreateOrder] 批量查询商品信息失败"
		return primitive.RollbackMessageState // 失败 撤销 事务消息
	}

	for _, good := range goods.Data {
		orderAmount += good.ShopPrice * float32(goodsNumsMap[good.Id]) // 计算此商品的价格 数量 * 价格
		orderGoods = append(orderGoods, &model.OrderGoods{
			//Order:      0,	订单表id
			Goods:      good.Id,
			GoodsName:  good.Name,
			GoodsImage: good.GoodsFrontImage,
			GoodsPrice: good.ShopPrice,
			Nums:       goodsNumsMap[good.Id],
		})

		goodsInvInfo = append(goodsInvInfo, &proto.GoodsInvInfo{
			GoodsId: good.Id,
			Num:     goodsNumsMap[good.Id],
		})
	}

	// 跨服务调用库存微服务进行库存扣减
	// 库存的扣减 - 访问库存服务 (跨库存服务)
	/*
		1. 调用库存服务的 trysell
		2. 调用仓库服务的 trysell
		3. 调用积分服务的 tryAdd
		任何一个服务出现了异常, 那么你得调用对应的所有的微服务 cancel接口
		如果所有的微服务都正常, 那么你得调用所有的微服务的confirm
	*/
	if _, err = global.InventorySrvClient.Sell(o.Ctx, &proto.SellInfo{GoodsInfo: goodsInvInfo, OrderSn: orderInfo.OrderSn}); err != nil {
		// 如果是因为网络问题, 这种如何避免误判
		o.Code = codes.ResourceExhausted
		o.Detail = "[CreateOrder] 扣减库存失败"
		if st, ok := status.FromError(err); ok {
			if st.Code() == codes.Unavailable {
				return primitive.UnknowState // 库存服务不可用 但是不清楚是 发送过去后 返回拥塞 还是库存服务宕机 情况
			}
		}
		return primitive.RollbackMessageState // 失败 撤销 事务消息
	}

	//o.Code = codes.Internal
	//o.Detail = "[CreateOrder] 创建订单失败"
	//return primitive.UnknowState

	// 生成订单表
	// 订单的基本信息表 - 订单的商品信息表
	tx := global.DB.Begin()

	_, spanCreateOrderInfo := otel.Tracer("").Start(o.Ctx, "create_OrderInfo")
	orderInfo.OrderMount = orderAmount
	if result := tx.Save(&orderInfo); result.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "[CreateOrder] 创建订单失败"
		return primitive.CommitMessageState // 失败 发送 事务消息
	}
	spanCreateOrderInfo.End()

	o.OrderAmount = orderAmount
	o.ID = orderInfo.ID
	for _, orderGood := range orderGoods {
		orderGood.Order = orderInfo.ID
	}
	// 批量插入 orderGoods
	_, spanCreateOrderGoods := otel.Tracer("").Start(o.Ctx, "create_OrderGoods")
	if result := tx.CreateInBatches(orderGoods, 100); result.RowsAffected == 0 { // 一次性插入100条数据 直到全部插入成功
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "[CreateOrder] 批量插入订单商品失败"
		return primitive.CommitMessageState // 失败 发送 事务消息
	}
	spanCreateOrderGoods.End()

	_, spanDeleteShoppingCart := otel.Tracer("").Start(o.Ctx, "delete_ShoppingCart")
	if result := tx.Where(&model.ShoppingCart{
		User:    orderInfo.User,
		Checked: true,
	}).Delete(&model.ShoppingCart{}); result.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "[CreateOrder] 删除购物车记录失败"
		return primitive.CommitMessageState // 失败 发送 事务消息
	}
	spanDeleteShoppingCart.End()

	// 发送延时消息
	p, _ := rocketmq.NewProducer(
		//producer.WithInstanceName(fmt.Sprintf("DelayTime%s", orderInfo.OrderSn)),
		producer.WithNsResolver(primitive.NewPassthroughResolver([]string{fmt.Sprintf("%s:%d", global.ServerConfig.RocketMq.Host, global.ServerConfig.RocketMq.Port)})),
		//producer.WithRetry(2),
	)
	if err := p.Start(); err != nil {
		panic(fmt.Sprintf("启动 producer 失败: %s", err))
	}
	ms := primitive.NewMessage("order_timeout", msg.Body)
	ms.WithDelayTimeLevel(16) // 30分钟
	_, err = p.SendSync(context.Background(), ms)
	if err != nil {
		tx.Rollback()
		o.Code = codes.Internal // 内部错误
		o.Detail = fmt.Sprintf("发送 延迟消息 失败: %s", err)
		return primitive.CommitMessageState // 失败 发送 事务消息
	}

	tx.Commit()
	o.Code = codes.OK
	return primitive.RollbackMessageState
}

func (o *OrderListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)

	// 检查之前的逻辑是否完成
	if result := global.DB.Where(model.OrderInfo{OrderSn: orderInfo.OrderSn}).First(&orderInfo); result.RowsAffected == 0 {
		return primitive.CommitMessageState // 查询不到订单详情 但是不能说明库存已经扣减 所以需要回滚
	}
	return primitive.RollbackMessageState // 查询到订单详情 说明 事务执行成功
}

// CreateOrder 新建订单
func (*OrderServer) CreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	/*
		1. 从购物车中获取选中的商品
		2. 商品的价格需要自己查询 - 访问商品服务 (跨商品服务)
		3. 库存的扣减 - 访问库存服务 (跨库存服务)
		4. 订单的基本信息表 - 订单的商品信息表
		5. 从购物车中删除已购买的记录
	*/
	orderListener := OrderListener{Ctx: ctx}
	OrderSn := GenerateOrderSn(req.UserId)
	p, err := rocketmq.NewTransactionProducer(
		&orderListener,
		producer.WithInstanceName(fmt.Sprintf("Transaction%s", OrderSn)),
		producer.WithNsResolver(primitive.NewPassthroughResolver([]string{fmt.Sprintf("%s:%d", global.ServerConfig.RocketMq.Host, global.ServerConfig.RocketMq.Port)})),
		//producer.WithRetry(1),
	)
	if err != nil {
		zap.S().Debugf("生成 producer 失败: %s", err.Error())
		return nil, err
	}

	if err = p.Start(); err != nil {
		zap.S().Debugf("启动 producer 失败: %s", err.Error())
		return nil, err
	}

	order := model.OrderInfo{
		User:    req.UserId, // 用户id
		OrderSn: OrderSn,    // 订单号
		//PayType:      "",	// 支付方式
		//Status:       "",	// 订单状态码
		//TradeNo:      "",	// 支付宝 订单号
		//OrderMount: orderAmount, // 订单需要支付的价格
		//PayTime:      time.Now(),
		Address:      req.Address,
		SignerName:   req.Name,
		SingerMobile: req.Mobile,
		Post:         req.Post,
	}

	jsonString, _ := json.Marshal(order)

	_, err = p.SendMessageInTransaction(context.Background(),
		primitive.NewMessage("order_reback", jsonString)) // 发送一个库存归还事务

	if err != nil {
		zap.S().Debug("[CreateOrder] Rocketmq 事务消息发送失败")
		return nil, status.Errorf(codes.Internal, "[CreateOrder] Rocketmq 事务消息发送失败")
	}
	if orderListener.Code != codes.OK { // 说明 执行事务失败了  回滚了
		zap.S().Debug(orderListener.Code, orderListener.Detail)
		return nil, status.Errorf(orderListener.Code, orderListener.Detail)
	}

	return &proto.OrderInfoResponse{
		Id:      orderListener.ID,
		OrderSn: order.OrderSn,
		Total:   orderListener.OrderAmount,
	}, nil
}

func (*OrderServer) OrderList(ctx context.Context, req *proto.OrderFilterRequest) (*proto.OrderListResponse, error) {
	var orders []model.OrderInfo
	var rsp proto.OrderListResponse
	var total int64
	// 不传递 userid 即默认为0, 因 gorm 规则为0的不会拼凑出这个查询语句 正好满足需求
	global.DB.Where(&model.OrderInfo{User: req.UserId}).Count(&total)
	rsp.Total = int32(total)

	// 分页
	global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Where(&model.OrderInfo{User: req.UserId}).Find(&orders)
	for _, order := range orders {
		rsp.Data = append(rsp.Data, &proto.OrderInfoResponse{
			Id:      order.ID,
			UserId:  order.User,
			OrderSn: order.OrderSn,
			PayType: order.PayType,
			Status:  order.Status,
			Total:   order.OrderMount,
			Address: order.Address,
			Name:    order.SignerName,
			Mobile:  order.SingerMobile,
			AddTime: order.CreatedAt.Format("2006-01-02 15:04:05"),
		})
	}
	return &rsp, nil
}

func (*OrderServer) OrderDetail(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoDetailResponse, error) {
	var order model.OrderInfo
	var orderGoods []model.OrderGoods
	var rsp proto.OrderInfoDetailResponse

	// 这个订单id 需要确是用户的订单, 如果在web层用户传递过来一个id的订单, web层应该先查询一下订单id是否是当前用户的订单
	if result := global.DB.Where(&model.OrderInfo{
		BaseModel: model.BaseModel{ID: req.Id},
		User:      req.UserId,
	}).First(&order); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}

	// 这里有一个坑 就是 proto.OrderInfoDetailResponse 中 OrderInfo 是 指针类型 所以不能直接赋值
	orderInfo := proto.OrderInfoResponse{}
	orderInfo.Id = order.ID
	orderInfo.UserId = order.User
	orderInfo.OrderSn = order.OrderSn
	orderInfo.PayType = order.PayType
	orderInfo.Status = order.Status
	orderInfo.Post = order.Post
	orderInfo.Total = order.OrderMount
	orderInfo.Address = order.Address
	orderInfo.Name = order.SignerName
	orderInfo.Mobile = order.SingerMobile
	rsp.OrderInfo = &orderInfo

	if result := global.DB.Where(&model.OrderGoods{Order: order.ID}).Find(&orderGoods); result.Error != nil {
		return nil, result.Error
	}
	for _, orderGood := range orderGoods {
		rsp.Goods = append(rsp.Goods, &proto.OrderItemResponse{
			//Id: orderGood.ID,
			//OrderId:    orderGood.Order,
			GoodsId:    orderGood.Goods,
			GoodsName:  orderGood.GoodsName,
			GoodsImage: orderGood.GoodsImage,
			GoodsPrice: orderGood.GoodsPrice,
			Num:        orderGood.Nums,
		})
	}

	return &rsp, nil
}

func (*OrderServer) UpdateOrderStatus(ctx context.Context, req *proto.OrderStatus) (*empty.Empty, error) {
	// 先查询, 再更新 实际上有两条sql执行, select 和 update 语句
	if result := global.DB.Model(&model.OrderInfo{}).Where("order_sn = ?", req.OrderSn).Update("status", req.Status); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}
	return &emptypb.Empty{}, nil
}

func OrderTimeout(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
	for i := range msgs {
		var orderInfo model.OrderInfo
		_ = json.Unmarshal(msgs[i].Body, &orderInfo)

		// 查询订单的支付状态, 如果已支付什么都不做, 如果未支付, 归还库存
		var order model.OrderInfo
		if result := global.DB.Where(model.OrderInfo{OrderSn: orderInfo.OrderSn}).First(&order); result.RowsAffected == 0 {
			return consumer.ConsumeSuccess, nil // 查询不到订单详情 但是不能说明库存已经扣减
		}
		if order.Status != "TRADE_SUCCESS" {
			tx := global.DB.Begin()
			// 归还库存	发送一个简单消息
			// 修改订单的状态为已关闭
			order.Status = "TRADE_CLOSED"
			tx.Save(&order)

			p, err := rocketmq.NewProducer(
				//producer.WithNameServer([]string{"192.168.16.154:9876"}),		// 和 下行代码类似 但是有点差别
				producer.WithNsResolver(primitive.NewPassthroughResolver([]string{fmt.Sprintf("%s:%d", global.ServerConfig.RocketMq.Host, global.ServerConfig.RocketMq.Port)})),
				//producer.WithRetry(2),
			)
			if err != nil {
				zap.S().Debugf(fmt.Sprintf("生成 producer 失败: %s", err))
			}

			if err = p.Start(); err != nil {
				zap.S().Debugf(fmt.Sprintf("启动 producer 失败: %s", err))
			}

			_, err = p.SendSync(context.Background(), primitive.NewMessage("order_reback", msgs[i].Body))
			if err != nil {
				tx.Rollback()
				zap.S().Debugf("发送 简单消息 失败: %s", err)
				return consumer.ConsumeRetryLater, nil // 发送 简单消息失败   让延迟消息重新发送然后重新执行此操作
			}

			if err = p.Shutdown(); err != nil {
				zap.S().Debug("关闭 producer 失败")
			}
			tx.Commit()
		}
	}
	return consumer.ConsumeSuccess, nil
}
